package org.mozilla.universalchardet

import org.mozilla.universalchardet.prober.CharsetProber
import org.mozilla.universalchardet.prober.MBCSGroupProber
import org.mozilla.universalchardet.prober.SBCSGroupProber
import org.mozilla.universalchardet.prober.EscCharsetProber
import org.mozilla.universalchardet.prober.Latin1Prober
import static org.mozilla.universalchardet.Constants.*

class UniversalDetector
{
	static final float SHORTCUT_THRESHOLD = 0.95f
	static final float MINIMUM_THRESHOLD = 0.20f

	enum InputState
	{
	    PURE_ASCII,
	    ESC_ASCII,
	    HIGHBYTE
	}

	private InputState  inputState
	private boolean     done
	private boolean     start
	private byte        lastChar
	private String      detectedCharset

	private List<CharsetProber> probers

	UniversalDetector()
	{
	    probers = [null, null, null]
	    reset()
	}

	boolean isDone()
	{
	    return done
	}

	/**
	 * @return The detected encoding is returned. If the detector couldn't
	 *          determine what encoding was used, null is returned.
	 */
	String getDetectedCharset()
	{
	    return detectedCharset
	}

	void handleData(final byte[] buf, int offset, int length)
	{
	    if (done)
	        return

	    if (start) {
	        start = false
	        if (length > 3) {
	            int b1 = buf[offset] & 0xFF
	            int b2 = buf[offset+1] & 0xFF
	            int b3 = buf[offset+2] & 0xFF
	            int b4 = buf[offset+3] & 0xFF

	            switch (b1) {
	            case 0xEF:
	                if (b2 == 0xBB && b3 == 0xBF) {
	                    detectedCharset = CHARSET_UTF_8
	                }
	                break
	            case 0xFE:
	                if (b2 == 0xFF && b3 == 0x00 && b4 == 0x00) {
	                    detectedCharset = CHARSET_X_ISO_10646_UCS_4_3412
	                } else if (b2 == 0xFF) {
	                    detectedCharset = CHARSET_UTF_16BE
	                }
	                break
	            case 0x00:
	                if (b2 == 0x00 && b3 == 0xFE && b4 == 0xFF) {
	                    detectedCharset = CHARSET_UTF_32BE
	                } else if (b2 == 0x00 && b3 == 0xFF && b4 == 0xFE) {
	                    detectedCharset = CHARSET_X_ISO_10646_UCS_4_2143
	                }
	                break
	            case 0xFF:
	                if (b2 == 0xFE && b3 == 0x00 && b4 == 0x00) {
	                    detectedCharset = CHARSET_UTF_32LE
	                } else if (b2 == 0xFE) {
	                    detectedCharset = CHARSET_UTF_16LE
	                }
	                break
	            }

	            if (detectedCharset != null) {
	                done = true
	                return
	            }
	        }
	    }

	    int maxPos = offset + length
	    for (int i=offset; i<maxPos; ++i) {
	        int c = buf[i] & 0xFF
	        if ((c & 0x80) != 0 && c != 0xA0) {
	            if (inputState != InputState.HIGHBYTE) {
	                inputState = InputState.HIGHBYTE

	                if (probers[0] == null) {
	                    probers[0] = new MBCSGroupProber()
	                }
	                if (probers[1] == null) {
	                    probers[1] = new SBCSGroupProber()
	                }
	                if (probers[2] == null) {
	                    probers[2] = new Latin1Prober()
	                }
	            }
	        } else {
	            if (inputState == InputState.PURE_ASCII &&
	                (c == 0x1B || (c == 0x7B && lastChar == 0x7E))) {
	                inputState = InputState.ESC_ASCII
	            }
	            lastChar = buf[i]
	        }
	    }

		def tmpProbers = []
		switch(inputState){
			case InputState.ESC_ASCII:
				tmpProbers << new EscCharsetProber()
				break
			case InputState.HIGHBYTE:
				tmpProbers = probers
				break
		}

		def prober = tmpProbers.find{ it.handleData(buf, offset, length) == CharsetProber.ProbingState.FOUND_IT }
		if( prober ) {
			done = true
			detectedCharset = prober.charSetName
		}
	}

	void dataEnd()
	{
	    if (inputState == InputState.HIGHBYTE) {
			def maxProber = probers.max{ it.confidence }
	        if (maxProber.confidence > MINIMUM_THRESHOLD)
	            detectedCharset = maxProber.charSetName
	    }
	}

	void reset()
	{
	    done = false
	    start = true
	    detectedCharset = null
	    inputState = InputState.PURE_ASCII
	    lastChar = 0

		probers.findAll{ it }.each{ it.reset() }
	}
}
